אוסף תרגילים למעבדה ביוניקס - 4 כתב וערך שייקה בילו תרגיל מספר 1 א. הבעיה של קוראים/כותבים (readers/writers) עם עדיפות לכותבים עוסקת בגישה של תהליכים למבנה נתונים משותף. סמן את הדרישות מפיתרון של הבעיה: א. יכול להיות לכל היותר כותב אחד בקטע הקריטי ב. יכול להיות לכל היותר קורא אחד בקטע הקריטי ג. יכולים להיות מספר כותבים בקטע הקריטי בו-זמנית ד. יכולים להיות מספר קוראים בקטע הקריטי בו-זמנית ה. אסור לכותבים וקוראים להיות בקטע הקריטי בו-זמנית ו. אם יש גם קוראים וגם כותבים המחכים להיכנס לקטע הקריטי,הכותבים מקבלים עדיפות ז. אסור להרעיב כותבים שמנסים להיכנס לקטע הקריטי ח. אסור להרעיב קוראים שמנסים להיכנס לקטע הקריטי ב. להלן הצעה לפתרון בעיית הקוראים/כותבים עם עדיפות לכותבים: //S is a semaphore initialized to 1; //writer: P(S); Critical Section V(S); wakeup_all_sleeping_processes(); //reader: while (S.value =< 0) { sleep(); P(S); Critical Section V(S); ג. סמן את הטענות הנכונות לגבי הפתרון הנ"ל,בהנחה שאכן יש גישה לערך של הסמפור,ושהסמפור הגון כלומר תהליך שמבצע P יכנס אחרי מספר סופי של פעולות V): א. הפתרון שגוי משום שהוא לא מאפשר ליותר מקורא אחד להיכנס לקטע הקריטי בו-זמנית ב. הפתרון שגוי משום שהוא לא מאפשר ליותר מכותב אחד להיכנס לקטע הקריטי בו-זמנית ג. הפתרון שגוי משום שיותר מקורא אחד יכול להיכנס לקטע הקריטי בו-זמנית ד. הפתרון שגוי משום שיותר מכותב אחד יכול להיכנס לקטע הקריטי בו-זמנית ה. הפתרון שגוי משום שגם קוראים וגם כותבים יכולים להיכנס לקטע הקריטי בו-זמנית ו. הפתרון שגוי משום שקורא יכול להיתקע בכניסה לקטע הקריטי לפרק זמן לא סופי ז. הפתרון שגוי משום שכותב יכול להיתקע בכניסה לקטע הקריטי לפרק זמן לא סופי 1
תרגיל מספר 2 לפניך תוכנית לדוגמה:יצרן-צרכן #include <stdio.h> #include <pthread.h> #include <semaphore.h> int num, // common variable of producer & consumer nprinted; // common flag: whether printing has been completed sem_t empty, // Binary sem.: Can producer write to 'num' avail; // Binary sem.: Can consumer read from 'num' // Printing monitor: pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t print_done = PTHREAD_COND_INITIALIZER; // Produces the numbers 1.. arg and writes them to 'num' void *producer(void *arg); // Consumes numbers from 'num' and prints them out as long as new ones // are produced: void *consumer(void *arg); int main() { pthread_t p, c; 2 sem_init(&empty, 0, 1); // // Initially, producer should write a // value to 'num' sem_init(&avail, 0, 0); // // Consumer must wait until first number // is available if (pthread_create(&p, NULL, producer, (void *) 100)) exit(1); if (pthread_create(&c, NULL, consumer, NULL)) exit(2); pthread_join(p, NULL); // NOTE: 'c' is not joined, as it performs an infinite loop; // however, it is cancelled when 'main' exits. Therefore, 'main' // must wait until last number is printed out sem_wait(&empty); // // Waiting until consumer reads last value of // 'num' into a local variable pthread_mutex_lock(&m); if (!nprinted) // if last number has not been printed yet pthread_cond_wait(&print_done, &m); pthread_mutex_unlock(&m);
sem_destroy(&empty); sem_destroy(&avail); void *producer(void *arg) { int max = (int) arg, i; for (i = 1; i <= max; i++) { sem_wait(&empty); num = i; sem_post(&avail); return NULL; void *consumer(void *arg) { int n; while (1) { sem_wait(&avail); n = num; nprinted = 0; // Preventing 'main' from finishing before last // number is printed out sem_post(&empty); printf("%d\n", n); pthread_mutex_lock(&m); nprinted = 1; // Since printing is done pthread_cond_signal(&print_done); // // For the case where 'main' // is already waiting pthread_mutex_unlock(&m); return NULL; משימות: 1. כתובאתהתוכנית ובצעהידור. 2. בדוקתנאיכניסה ובדוקתקינותקלט. 3. הסברבכמהמיליםמהעושההתוכנית. 4. הסברמהיהיההפלטשלהבסיוםהריצה. 5. הסברמהוסמפורולמהמשמשהסמפורבתוכניתזו..6 הסברמהו?mutex 3
תרגיל מספר 3 לפניך תוכנית המציגה תהליכי מיון מיזוג (Mergesort) בתרגיל זה עליכם לממש את שיטת מיוןמיזוג (Mergesort) באופן מקבילי בעזרת תהליכוני.(POSIX Threads) POSIX לשם כך יש להוסיף בקובץ המקור את השורה: #include <pthread.h> בנוסף, יש להוסיף בשורת-הפקודה להידור את הדגל l עם הארגומנט ;pthread כלומר, שורת-הפקודה תהיה, לדוגמה: > gcc o exec-file l pthread source-file התכנית תקלוט, לתוך מערך, רשימה של מספרים שלמים מטיפוס,long המופרדים בריווח לבן space) = white רווח, טאב או.(newline בכל שלב, ימוינו בנפרד שני חצאי המערך, וימוזגו לאחר מכן. מיון של כל חצי כזה יתבצע ע"י תהליכון חדש. במקרה של מספר אי-זוגי של איברים, ייחצה התחום כך שהחלק הגדול יותר יהיה בצד שמאל (האינדקסים הקטנים יותר). מובן, שאין צורך ליצור תהליכון חדש עבור מערך בגודל 1, שהרי הוא כבר ממוין, בהגדרה. התכנית תקלוט חסם עליון למספר האיברים, שמותר לטפל בהם באופן מקבילי *. במקרה שמספר האיברים באחריותו של תהליכון מסוים גדול מחסם זה, ימתין התהליכון לסיום תהליכון-הבן הראשון (האחראי על מיון חציו השמאלי של המערך), ורק אחר-כך ייצור את תהליכון-הבן השני (האחראי על מיון חציו הימני). מיד בתום ההמתנה, לפני יצירת תהליכון-הבן השני (אם בכלל), תודפס בשורה נפרדת ההודעה: [l h] joined כאשר l ו- h מייצגים את האינדקס הנמוך ביותר ואת האינדקס הגבוה ביותר, בהתאמה, בחצי המערך השמאלי, שמיונו הסתיים זה עתה. במקרה של טיפול מקבילי, לא תודפס ההודעה הנ"ל (ראה דוגמה להלן). המיון יתבצע בעזרת שני מערכים בגודל שווה, אשר יוקצו בצורה דינאמית (ע"י פונקצית הספרייה (malloc וינוהלו ע"י שני מצביעים גלובאליים. למערך אחד ייקלטו כל מספרי הקלט, ואילו המערך השני ישמש כמערך-עזר בתהליך המיון. מספר נתוני הקלט יועבר דרך שורת-הפקודה (ראה להלן). האיברים עצמם ייקלטו מן הקלט התקני באמצעות scanf עם."%ld" מובן, כי ניתן יהיה להפנות קלט מקובץ בעזרת סימן '>' בשורת-הפקודה. במקרה של אי-התאמה בין מספר הקלטים המוצהר לבין מספרם בפועל, תדפיס התכנית בשורה נפרדת את ההודעה: Wrong number of inputs ותסתיים מיד עם ערך יציאה 1. הערה: כאשר מגיעה פונקציה scanf לסוף קובץ הקלט, היא מחזירה את התו.EOF בעבודה הידודית (כלומר, הקלט התקני מגיע מהמקלדת), ניתן לציין את סוף הקלט ע"י הקשת.Ctrl-d כאשר מסתיים מיון קטע המערך l h ע"י תהליכון מסוים, תודפס השורה:.[l h] ובשורה הבאה איברי הקטע לאחר המיון, כאשר רווח (' ') מפריד בין איבר לאיבר. כל ההדפסות בתכנית, למעט במקרי שגיאה בקלט, תתבצענה ע"י תהליכון מיוחד, המיועד אך ורק למטרה זו. כל תהליכון אחר, המעוניין בהדפסה, יעביר לתהליכון זה את הנתונים הנחוצים דרך שלושה משתנים משותפים:.1 l אינדקסנמוך (שמאלי). * כדי למנוע קיום מספר רב מדי של תהליכונים בעת ובעונה אחת. 4
.2 h אינדקסגבוה (ימני)..3 message משתנהמסוג enum,char וכדומה, אשריצייןהדפסתהודעתצירוף joined") ("[l h] אותוצאתמיון, כמפורטלעיל. תיאום הקריאה והכתיבה בין התהליכונים יתבצע באמצעות שני סמפורים בינאריים: האחד (empty) למניעת כתיבה של נתונים חדשים בטרם טופלו הנוכחיים, והאחר (avail) למניעת קריאה בטרם הוכנסו נתונים חדשים. בנוסף לכך, יש להגדיר פקח,(Monitor) אשר יבטיח כי התכנית לא תסתיים בטרם יודפס הפלט האחרון. צורך זה נובע מהעובדה, שתהליכון ההדפסה אינו יודע מראש בכמה קלטים יצטרך לטפל. לכן, עליו לבצע לולאה אינסופית, אשר תופסק ע"י התהליכון הראשי רק לאחר צירוף כל תהליכוני החישוב. ואולם, אין לסיים את התכנית לפני שיודפסו כל הפלטים. מכאן, שאם טרם הודפס הפלט האחרון, על התהליכון הראשי להמתין לאירוע זה. בנספח נתונה תכנית, המדגימה מימוש של יצרן-צרכן בעל תכונות דומות. ככלל, עליכםלדאוגלכך, שקטעיםקריטייםיהיוקצריםככלהאפשר. #include <semaphore.h> command-name max-parallel number-of-elements כדי להשתמש בסמפורים, יש להוסיף בקובץ המקור את השורה: תחביר הפקודה: :command-name שם הפקודה,a.out) mergesort או כל שם שיינתן לקובץ הריצה). :max-parallel חסם עליון למספר האיברים למיון מקבילי; מספר חיובי בלבד. :number-of-elements מספר הקלטים להם מצפה התכנית (נדרש לצורך הקצאת שני המערכים בגודל זה); מספר חיובי בלבד. בכל מקרה של ארגומנטים בלתי-תקינים (כולל מחסור/עודף בארגומנטים), תודפס ההודעה: Usage: command-name <max-parallel> <number-of-elements> Both arguments must be positive integers והתכנית תסתיים מיד עם ערך יציאה 2 (במקום command-name יודפס שם קובץ הריצה.(argv[0] 5
> mergesort 5 10 5 2 3 7 0 9 4 12 1 3 // various white spaces [0 1] 2 5 [3 4] 0 7 [0 2] 3 2 5 [0 4] 3 0 2 5 7 [0 4] joined [5 6] 9 4 [5 7] 9 4 12 [8 9] 1 3 [5 9] 9 1 3 4 12 [0 9] 9 3 1 0 2 3 4 5 7 12 דוגמתריצה בהצלחה! 6